package com.ruoyi.web.controller.common;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.outpatient.domain.*;
import com.ruoyi.outpatient.mapper.*;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author 伟峰
 * @date 2022/5/9
 * @description: 推荐算法的定时任务
 */

@Component("recommendTask")
public class RecommendTask {

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private IBlogMapper blogMapper;

    @Autowired
    private IBlogTagMapper blogTagMapper;

    @Autowired
    private ITagMapper tagMapper;

    @Autowired
    private ISimilarityMapper similarityMapper;

    @Autowired
    private IActiveUserMapper activeUserMapper;
    @Autowired
    private IPersonLoginMapper personLoginMapper;



    //    内容推荐算法
    public void ryNoParams() {



//        获取活跃用户：一个月内登录天数超过5天的
//        redisCache.getBig("sign",);
        //        获取活跃用户的id
//        1百万用户（包括不活跃）
        List<Long> list=activeUserMapper.selectAllId();




//        2.一百万blog的标签         不使用多表联查,单表组装减少性能损耗
        List<Blog> tagList = blogMapper.selectAllTag();
//        List<Long> blogId=blogMapper.selectAllBlogId(); //一百万blog的标签
////       在blogid上加索引
//        List<BlogTag> blogTags= blogTagMapper.selectAllTagId(blogId);
////        在tagName加索引,使成覆盖索引
//        List<Blog> blogs=blogMapper.selectAllTagById(blogTags);


//        3.封装相似度
        List<Similarity> doubles=new ArrayList<>();


//        进行O(N2)变量排序
//       4. 用户1:{标签1，标签2，标签3}分子
        for (int i = 0; i < list.size(); i++) {

//            4.1 用户对应全部热门标签
            Long tagSumsx = redisCache.getCacheZSetZCard("recommend:tagscore:" + list.get(i));
//            4.2取出前50%的标签  用户1:{标签1，标签2，标签3}
            Set<ZSetOperations.TypedTuple> setx = redisCache.reverseRangeZSetWithScores("recommend:tagscore:" + list.get(i), 0, tagSumsx.intValue() > 10 ? 10 : tagSumsx.intValue()+1);


//            4.3将取出来的标签转为hashmap{"标签名",分数}
            Map<String, Double> map = new HashMap<>();
            if (setx==null){
                continue;
            }
            for (ZSetOperations.TypedTuple f : setx) {
                map.put(f.getValue().toString(), f.getScore());
            }



//            4.4将hashmap的键(标签名)提取出来
            Set<String> strings = map.keySet();


//            4.5从数据库取出的第一个blog对应的标签集合
            for (int t = 0; t < tagList.size(); t++) {

                Iterator it = strings.iterator();//获取Iterator对象

//                4.5.1封装第一个分母
                Double denominator = 0.0;
                while (it.hasNext()) {

                    Object key = it.next();
                    Double val = map.get(key);
//                    4.5.2 分母开平方不断的累加
                    denominator += Math.pow(val, 2);
                    System.out.println(key + " : " + val);
                }

//                4.6 封装分子
                Double numerator = 0.0;
                for (int j = 0; j < tagList.get(t).getTags().size(); j++) {
//                    4.6.1只要该blog的tag集合中有一个为空就跳出循环
                    if (tagList.get(t).getTags().get(j) == null) {
                        continue;
                    }
//                与hashmap中的键进行匹配

//                    4.6.2 循环出blog的tagName
                    String tagName = tagList.get(t).getTags().get(j).getTagName();
//                    4.6.3 只要用户本身的标签包含就进行分数累加
                    if (strings.contains(tagName)) {
                        numerator += map.get(tagName);
                    }

//                    if (map.get(tagList.get(t).getTags().get(j).getTagName())){
//                        continue;
//                    }
//                    denominator += Math.pow(map.get(tagList.get(t).getTags().get(j).getTagName()), 2);
//                }
//                if (numerator == 0) {
//                    continue;
//                }
                }


//                  第二个分母     找出第二个分母的长度即 tag数量开平方
                int sizes = tagList.get(t).getTags().size();
//               4.7   进行余旋相似度算法求出相似度
                Double similarity = numerator / ((Math.sqrt(denominator)) * (Math.sqrt(sizes)));
//                BigDecimal bigDecimal = BigDecimal.valueOf(similarity).setScale(2);
//                double v = bigDecimal.doubleValue();
                int ii=0;
//                4.8 如果分子为0 必为0就没必要添加了
                if (numerator!=0){
                    doubles.add(new Similarity(list.get(i),tagList.get(t).getBlogId(),similarity));
//                    doubles.add( list.get(i)+":"+tagList.get(t).getBlogId()+":"+similarity);
                }

            }


        }

//        将相似度插入数据库
        int i=similarityMapper.insert(doubles);

        for (Similarity d:doubles)
        {

//            将相似度变量出
            System.out.println(d.getSimilar());
        }


        //        Set<Long> set = maps.keySet();//先使用set得到map的所有key
//        Iterator<Long> it = set.iterator();//构造set的迭代器
//        while (it.hasNext()) {
//            Long key = it.next();
//            Object value = maps.get(key);
//            System.out.println("key" + key + "value" + value);
//        }
    }





//    每个月统计一次活跃用户
    public void activeUser() {


//       1. 将2022-04-21截取出来
        String today= DateUtil.today();
        String[] split = today.split("-");
////        优化：不要同时setbin多个,要使用管道
//        redisCache.setBig("sign:"+3+":"+split[0]+":"+split[1],Long.valueOf(split[2]).longValue()-1,true);


//       2. 将所有personId找出来
      List<Long> list= personLoginMapper.selectPersonId();



//        进行统计,只要这个月登录次数超过5天,就存入数据库中
        ArrayList<Long> arrayList = new ArrayList<>();
//        3.遍历所有用户进行统计
        for (Long lid:list)
        {

//           3.1 从月末到1进行统计     signActiveUser:3L:2022:03,27,0
            long bigsCount = redisCache.getBigsCount("signActiveUser:" + lid + ":" + split[0] + ":" + split[1], Integer.parseInt(split[2]) - 1, 0);

//           3.2 为0说明一次都没有登录过,下一个用户
            if (bigsCount==0){
                continue;
            }

            int i=0;
            long j=0;
            while (true){
//               3.3 bigsCount十进制显示： 15456 & 1 如果为1就统计
                if ((bigsCount & 1)==1){
                    i++;
                }
//               3.4 然后无符号 右移一位
                long l = bigsCount >>>= 1;

//               3.5 如果为0说明已经移到全部为0了 他是十进制的 19943
                if (l==0) {
                    break;
                }
            }
//            4. i>3说明登录天数超过3 则添加为活跃用户
            if (i>3){
                arrayList.add(lid);
            }
////            每一万条插入一次,30万性能最佳
        }

//        5.将所有的用户ID批量进行插入
        int i=activeUserMapper.insert(arrayList);
//        Long activeUsers = redisCache.setCacheSets("ac",  JSON.toJSON(list).toString());

        System.out.println(i);
//        redisCache.expire("ac",100, TimeUnit.MINUTES);

    }


}


